(library (list-helpers)
  (export flatten
          make-list
          split-off-n
          split-into-n-sized-segments
          split-at
          split-into-segments
          split-into-n-segments)
  (import
   (except (rnrs base) let-values map error)
   (only (guile)
         lambda* λ
         remainder
         round
         simple-format)
   ;; receive form
   (srfi srfi-8))


  (define flatten
    (λ (lst)
      (cond [(null? lst) '()]
            [(pair? lst)
             (append (flatten (car lst))
                     (flatten (cdr lst)))]
            [else
             (list lst)])))


  (define make-list
    (λ (val count)
      (cond
       [(> count 0)
        (cons val (make-list val (- count 1)))]
       [else '()])))


  (define split-off-n
    (λ (lst n)
      "Take a list LST and split off the first N elements. Return
2 values: The split off part and the remaining part of the
list LST."
      (let iter ([lst° lst]
                 [counter 0]
                 [cont
                  (λ (acc-split-off rem-lst)
                    (values acc-split-off rem-lst))])
        (cond
         [(null? lst°)
          (cont '() lst°)]
         [(= counter n)
          (cont '() lst°)]
         [else
          (iter (cdr lst°)
                (+ counter 1)
                (λ (new-tail rem-lst)
                  (cont (cons (car lst°) new-tail)
                        rem-lst)))]))))


  (define split-into-n-sized-segments
    (λ (lst n)
      (cond
       [(null? lst) '()]
       [else
        (receive (split-off-part remaining-list)
            (split-off-n lst n)
          (cons split-off-part
                (split-into-n-sized-segments remaining-list n)))])))


  (define split-at
    (λ (lst pred)
      "Each element of LST is checked using the predicate. If the
predicate is satisfied for an element, then that element
will be seen as the separator. Return 2 values: The split
off part and the remaining part of the list LST."
      (let iter ([lst° lst]
                 [index° 0]
                 [cont
                  (λ (acc-split-off rem-lst)
                    (values acc-split-off rem-lst))])
        (cond
         [(null? lst°)
          (cont '() lst°)]
         [(pred (car lst°) index°)
          (cont '() (cdr lst°))]
         [else
          (iter (cdr lst°)
                (+ index° 1)
                (λ (new-tail rem-lst)
                  (cont (cons (car lst°) new-tail)
                        rem-lst)))]))))


  (define split-into-segments
    (λ (lst pred)
      "Split the list LST into segments based on a predicate for
an element being a separator or not. Each element is checked
using the predicate. If the predicate is satisfied for an
element, then that element will be seen as a separator and
will not be part of the resulting segments."
      (cond
       [(null? lst) '()]
       [else
        (receive (split-off-part remaining-list) (split-at lst pred)
          (cons split-off-part
                (split-into-segments remaining-list pred)))])))


  (define split-into-n-segments
    (λ (lst n)
      "Try to split list LST into N segments. The function tries
to make the segments as evenly sized as possible."
      (let ([chunk-size (round (/ (length lst) n))])
        (let iter ([lst° lst] [counter 0])
          (cond
           [(= counter (- n 1)) (list lst°)]
           [(and (< counter n) (null? lst°))
            (cons '() (iter lst° (+ counter 1)))]
           [(null? lst°) '()]
           [else
            (receive (split-off-part remaining-list) (split-off-n lst° chunk-size)
              (cons split-off-part
                    (iter remaining-list (+ counter 1))))]))))))
